package org.hjl.commonutils.encryption;

import lombok.extern.slf4j.Slf4j;
import org.hjl.commonutils.exception.GlobalException;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;


/**
 * @author admin
 */
@Slf4j
public class RSAUtil {

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * Method: decryptBASE64 <br/>
     * description: 解码返回byte <br/>
     *
     * @param key 向量
     */
    public static byte[] decryptBase64(String key) {
        return Base64.getMimeDecoder().decode(key);
    }

    /**
     * Method: encryptBASE64 <br/>
     * description: 编码返回字符串 <br/>
     *
     * @param key 向量
     */
    public static String encryptBase64(byte[] key) {
        return Base64.getEncoder().encodeToString(key);
    }

    /**
     * 获取base64加密后的字符串的原始公钥
     */
    public static Key getPublicKeyFromBase64KeyEncodeStr(String keyStr) throws Exception {
        byte[] keyBytes = decryptBase64(keyStr);
        // 取得公钥
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        return KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(x509KeySpec);
    }

    /**
     * 获取base64加密后的字符串的原始私钥
     */
    public static Key getPrivateKeyFromBase64KeyEncodeStr(String keyStr) throws Exception {
        byte[] keyBytes = decryptBase64(keyStr);
        // 取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        return KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(pkcs8KeySpec);
    }

    /**
     * Method: encrypt <br/>
     * description: 公钥分段加密 <br/>
     *
     * @param dataStr      加密内容，明文
     * @param publicKeyStr 公钥内容
     * @return 密文
     */
    public static String encrypt(String dataStr, String publicKeyStr) {
        log.info("公钥分段加密开始: {}", dataStr);
        String encodedDataStr;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] data = dataStr.getBytes(StandardCharsets.UTF_8);
            // 获取原始公钥
            Key decodePublicKey = getPublicKeyFromBase64KeyEncodeStr(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, decodePublicKey);
            int inputLen = data.length;
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            encodedDataStr = encryptBase64(encryptedData);
            System.out.println("公钥分段加密完毕");
        } catch (Exception e) {
            throw new GlobalException("加密失败!");
        }
        return encodedDataStr;
    }

    /**
     * Method: encrypt <br/>
     * description: 私钥分段解密 <br/>
     *
     * @param dataStr    解密内容，密文
     * @param privateKey 私钥
     * @return 明文
     */
    public static String decrypt(String dataStr, String privateKey) {
        log.debug("私钥分段解密处理开始: {}", dataStr);
        String decodedDataStr;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {

            byte[] encryptedData = decryptBase64(dataStr);
            // 获取原始私钥
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key decodePrivateKey = getPrivateKeyFromBase64KeyEncodeStr(privateKey);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, decodePrivateKey);
            int inputLen = encryptedData.length;

            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            decodedDataStr = out.toString(StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new GlobalException("解密失败!");
        }
        return decodedDataStr;
    }

}
